require "test/unit"

require "fileutils"
require "tmpdir"

require "test-unit-ext/attributes"

module Test
  module Unit
    class TestCase
      class << self
        def priority(name, *tests)
          singleton_class = (class << self; self; end)
          priority_check_method = priority_check_method_name(name)
          unless singleton_class.private_method_defined?(priority_check_method)
            raise ArgumentError, "unknown priority: #{name}"
          end
          attribute(:priority, name, {:keep => true}, *tests)
        end

        def need_to_run?(test_name)
          priority = (attributes(test_name) || {})[:priority] || :normal
          __send__(priority_check_method_name(priority), test_name)
        end

        private
        def priority_check_method_name(priority_name)
          "run_priority_#{priority_name}?"
        end

        def set_priority(name, priority=@current_priority)
          @priority_table[normalize_test_name(name)] = priority
        end

        def run_priority_must?(test_name)
          true
        end

        def run_priority_important?(test_name)
          rand > 0.1
        end

        def run_priority_high?(test_name)
          rand > 0.3
        end

        def run_priority_normal?(test_name)
          rand > 0.5
        end

        def run_priority_low?(test_name)
          rand > 0.75
        end

        def run_priority_never?(test_name)
          false
        end
      end

      def need_to_run?
        !previous_test_success? or self.class.need_to_run?(@method_name)
      end

      alias_method :run_without_priority, :run
      def run(result, &block)
        run_without_priority(result, &block)
      ensure
        if passed?
          FileUtils.touch(passed_file)
        else
          FileUtils.rm_f(passed_file)
        end
      end

      private
      def previous_test_success?
        File.exist?(passed_file)
      end

      def result_dir
        components = [".test-result", self.class.name, @method_name.to_s]
        parent_directories = [File.dirname($0),
                              File.join(File.dirname(__FILE__), ".."),
                              Dir.pwd]
        if Process.respond_to?(:uid)
          parent_directories << File.join(Dir.tmpdir, Process.uid.to_s)
        end
        parent_directories.each do |parent_directory|
          dir = File.expand_path(File.join(parent_directory, *components))
          begin
            FileUtils.mkdir_p(dir)
            return dir
          rescue Errno::EACCES
          end
        end

        raise Errno::EACCES, parent_directories.join(", ")
      end

      def passed_file
        File.join(result_dir, "passed")
      end

      def escaped_method_name
        @method_name.to_s.gsub(/[!?]$/) do |matched|
          case matched
          when "!"
            ".destructive"
          when "?"
            ".predicate"
          end
        end
      end
    end

    class TestSuite
      @@priority_mode = false

      class << self
        def priority_mode=(bool)
          @@priority_mode = bool
        end
      end

      alias_method :run_without_priority_support, :run
      def run(*args, &block)
        priority_mode = @@priority_mode
        if priority_mode
          @original_tests = @tests
          apply_priority
        end
        run_without_priority_support(*args, &block)
      ensure
        @tests = @original_tests if priority_mode
      end

      def apply_priority
        @tests = @tests.reject {|test| !test.need_to_run?}
      end

      def need_to_run?
        apply_priority
        !@tests.empty?
      end
    end

    class AutoRunner
      alias_method :options_without_priority, :options
      def options
        opts = options_without_priority
        opts.on("--[no-]priority", "use priority mode") do |bool|
          TestSuite.priority_mode = bool
        end
        opts
      end
    end
  end
end
